#include "wm_dma.h"
#include "wm_cpu.h"

static void DMA_SetConfig(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint16_t DataLength);

/**
 * @breief   Initialize the DMA according to the specified
 *           parameters in the DMA_InitTypeDef and initialize the associated handle.
 * @param    hdma: Pointer to a DMA_HandleTypeDef structure that contains
 *                 the configuration information for the specified DMA Channel.
 * @retval   HAL status
 */
HAL_StatusTypeDef HAL_DMA_Init(DMA_HandleTypeDef *hdma)
{
    uint32_t temp = 0;
    DMA_LinkDescriptor *desc;
    
    if (hdma == NULL)
    {
        return HAL_ERROR;
    }
    
    assert_param(IS_DMA_ALL_INSTANCE(hdma->Instance));
    assert_param(IS_DMA_DIRECTION(hdma->Init.Direction));
    assert_param(IS_DMA_DEST_INC_STATE(hdma->Init.DestInc));
    assert_param(IS_DMA_SRC_INC_STATE(hdma->Init.SrcInc));
    assert_param(IS_DMA_DATA_SIZE(hdma->Init.DataAlignment));
    assert_param(IS_DMA_MODE(hdma->Init.Mode));
    
    hdma->DmaBaseAddress = DMA;
    hdma->ChannelIndex = (((uint32_t)hdma->Instance - (uint32_t)DMA_Channel0) / ((uint32_t)DMA_Channel1 - (uint32_t)DMA_Channel0));
    
    hdma->State = HAL_DMA_STATE_BUSY;
    CLEAR_REG(hdma->Instance->CR2);
    CLEAR_REG(hdma->Instance->MODE);
    WRITE_REG(hdma->Instance->MODE, hdma->Init.Direction);
    if ((hdma->Init.Direction == DMA_PERIPH_TO_MEMORY) || (hdma->Init.Direction == DMA_MEMORY_TO_PERIPH))
    {
        assert_param(IS_DMA_REQUEST_SOURCE(hdma->Init.RequestSourceSel));
        MODIFY_REG(hdma->Instance->MODE, DMA_MODE_CH, hdma->Init.RequestSourceSel);
        if ((hdma->Init.RequestSourceSel == DMA_REQUEST_SOURCE_UART_RX) || (hdma->Init.RequestSourceSel == DMA_REQUEST_SOURCE_UART_TX))
        {
            assert_param(IS_DMA_UART_CHANNEL(hdma->Init.RequestUartSel));
            MODIFY_REG(hdma->DmaBaseAddress->REQCH, DMA_REQCH_UART, hdma->Init.RequestUartSel);
        }
    }
    if ((hdma->Init.Mode == DMA_MODE_NORMAL_SINGLE) || (hdma->Init.Mode == DMA_MODE_NORMAL_CIRCULAR))
    {
        WRITE_REG(hdma->Instance->CR2, (hdma->Init.DataAlignment | hdma->Init.DestInc | hdma->Init.SrcInc));
        if (hdma->Init.Mode == DMA_MODE_NORMAL_CIRCULAR)
        {
            SET_BIT(hdma->Instance->CR2, DMA_CR2_AUTORELOAD);
        }
    }
    else if ((hdma->Init.Mode == DMA_MODE_LINK_SINGLE) || (hdma->Init.Mode == DMA_MODE_LINK_CIRCULAR))
    {
        SET_BIT(hdma->Instance->MODE, (DMA_MODE_LNM | DMA_MODE_LINK));
        desc = hdma->LinkDesc;
        temp = (hdma->Init.DataAlignment | hdma->Init.DestInc | hdma->Init.SrcInc) >> 1;
        desc[0].Valid = 0;
        desc[0].Control = temp;
        desc[0].Next = (DMA_LinkDescriptor *)&desc[1];
        
        desc[1].Valid = 0;
        desc[1].Control = temp;
        if (hdma->Init.Mode == DMA_MODE_LINK_CIRCULAR)
        {
            desc[1].Next = (DMA_LinkDescriptor *)&desc[0];
        }
        else if(hdma->Init.Mode == DMA_MODE_LINK_SINGLE)
        {
            desc[1].Next = NULL;
        }
    }
    
    hdma->ErrorCode = HAL_DMA_ERROR_NONE;
    hdma->State = HAL_DMA_STATE_READY;
    hdma->Lock = HAL_UNLOCKED;
    
    return HAL_OK;
}

/**
  * @brief  DeInitialize the DMA peripheral.
  * @param  hdma: pointer to a DMA_HandleTypeDef structure that contains
  *               the configuration information for the specified DMA Channel.
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_DMA_DeInit (DMA_HandleTypeDef *hdma)
{
    if (hdma == NULL)
    {
        return HAL_ERROR;
    }
    
    assert_param(IS_DMA_ALL_INSTANCE(hdma->Instance));
    
    __HAL_DMA_DISABLE(hdma);
    
    hdma->Instance->SA = 0U;
    hdma->Instance->DA = 0U;
    hdma->Instance->SWA = 0U;
    hdma->Instance->DWA = 0U;
    hdma->Instance->LA = 0U;
    hdma->Instance->CR1 = 0U;
    hdma->Instance->MODE = 0U;
    hdma->Instance->CR2 = 0U;
    
    hdma->DmaBaseAddress->IF = ((DMA_IF_TRANSFER_DONE | DMA_IF_BURST_DONE) << (hdma->ChannelIndex * 2));
    
    hdma->ErrorCode = HAL_DMA_ERROR_NONE;
    hdma->State = HAL_DMA_STATE_RESET;
    __HAL_UNLOCK(hdma);
    
    return HAL_OK;
}

/**
  * @brief  Start the DMA Transfer.
  * @param  hdma: pointer to a DMA_HandleTypeDef structure that contains
  *               the configuration information for the specified DMA Channel.
  * @param  SrcAddress: The source memory Buffer address
  * @param  DstAddress: The destination memory Buffer address
  * @param  DataLength: The length of data to be transferred from source to destination. Unit: Byte
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_DMA_Start(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint16_t DataLength)
{
    HAL_StatusTypeDef status = HAL_OK;
    
    assert_param(IS_DMA_BUFFER_SIZE(DataLength));
    
    __HAL_LOCK(hdma);
    if (HAL_DMA_STATE_READY == hdma->State)
    {
        hdma->State = HAL_DMA_STATE_BUSY;
        hdma->ErrorCode = HAL_DMA_ERROR_NONE;
        
        __HAL_DMA_DISABLE(hdma);
        DMA_SetConfig(hdma, SrcAddress, DstAddress, DataLength);
        __HAL_DMA_ENABLE(hdma);
    }
    else
    {
        __HAL_UNLOCK(hdma);
        status = HAL_BUSY;
    }
    
    return status;
}

/**
  * @brief  Start the DMA Transfer with interrupt enabled.
  * @param  hdma: pointer to a DMA_HandleTypeDef structure that contains
  *               the configuration information for the specified DMA Channel.
  * @param  SrcAddress: The source memory Buffer address
  * @param  DstAddress: The destination memory Buffer address
  * @param  DataLength: The length of data to be transferred from source to destination. Unit: Byte
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_DMA_Start_IT(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint16_t DataLength)
{
    HAL_StatusTypeDef status = HAL_OK;
    
    assert_param(IS_DMA_BUFFER_SIZE(DataLength));
    
    __HAL_LOCK(hdma);
    if (HAL_DMA_STATE_READY == hdma->State)
    {
        hdma->State = HAL_DMA_STATE_BUSY;
        hdma->ErrorCode = HAL_DMA_ERROR_NONE;
        
        __HAL_DMA_DISABLE(hdma);
        DMA_SetConfig(hdma, SrcAddress, DstAddress, DataLength);
        __HAL_DMA_ENABLE_IT(hdma, (DMA_FLAG_TF_DONE << (hdma->ChannelIndex * 2)));
        __HAL_DMA_ENABLE(hdma);
    }
    else
    {
        __HAL_UNLOCK(hdma);
        status = HAL_BUSY;
    }
    
    return status;
}

/**
  * @brief  Abort the DMA Transfer.
  * @param  hdma: pointer to a DMA_HandleTypeDef structure that contains
  *               the configuration information for the specified DMA Channel.
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_DMA_Abort(DMA_HandleTypeDef *hdma)
{
    HAL_StatusTypeDef status = HAL_OK;
    
    if (hdma->State != HAL_DMA_STATE_BUSY)
    {
        hdma->ErrorCode = HAL_DMA_ERROR_NO_XFER;
        __HAL_UNLOCK(hdma);
        
        return HAL_ERROR;
    }
    else
    {
        __HAL_DMA_DISABLE_IT(hdma, ((DMA_IF_TRANSFER_DONE | DMA_IF_BURST_DONE) << (hdma->ChannelIndex * 2)));
        __HAL_DMA_DISABLE(hdma);
        __HAL_DMA_CLEAR_FLAG(hdma, ((DMA_IF_TRANSFER_DONE | DMA_IF_BURST_DONE) << (hdma->ChannelIndex * 2)));
    }
    hdma->State = HAL_DMA_STATE_READY;
    __HAL_UNLOCK(hdma);
    
    return status;
}

/**
  * @brief  Aborts the DMA Transfer in Interrupt mode.
  * @param  hdma  : pointer to a DMA_HandleTypeDef structure that contains
  *                 the configuration information for the specified DMA Channel.
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_DMA_Abort_IT(DMA_HandleTypeDef *hdma)
{
    HAL_StatusTypeDef status = HAL_OK;
    
    if (hdma->State != HAL_DMA_STATE_BUSY)
    {
        hdma->ErrorCode = HAL_DMA_ERROR_NO_XFER;
        
        status = HAL_ERROR;
    }
    else
    {
        __HAL_DMA_DISABLE_IT(hdma, ((DMA_IF_TRANSFER_DONE | DMA_IF_BURST_DONE) << (hdma->ChannelIndex * 2)));
        __HAL_DMA_DISABLE(hdma);
        __HAL_DMA_CLEAR_FLAG(hdma, ((DMA_IF_TRANSFER_DONE | DMA_IF_BURST_DONE) << (hdma->ChannelIndex * 2)));
        hdma->State = HAL_DMA_STATE_READY;
        __HAL_UNLOCK(hdma);
        if (hdma->XferAbortCallback != NULL)
        {
            hdma->XferAbortCallback(hdma);
        }
    }
    
    return status;
}

/**
  * @brief  Polling for transfer complete.
  * @param  hdma:    pointer to a DMA_HandleTypeDef structure that contains
  *                  the configuration information for the specified DMA Channel.
  * @param  CompleteLevel: Specifies the DMA level complete.
  * @param  Timeout:       Timeout duration.
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_DMA_PollForTransfer(DMA_HandleTypeDef *hdma, HAL_DMA_LevelCompleteTypeDef CompleteLevel, uint32_t Timeout)
{
    uint32_t tickstart = 0U;
    uint32_t temp;
    
    if (HAL_DMA_STATE_BUSY != hdma->State)
    {
        hdma->ErrorCode = HAL_DMA_ERROR_NO_XFER;
        __HAL_UNLOCK(hdma);
        
        return HAL_ERROR;
    }
    
    if ((hdma->Init.Mode == DMA_MODE_NORMAL_CIRCULAR) || (hdma->Init.Mode == DMA_MODE_LINK_CIRCULAR))
    {
        hdma->ErrorCode = HAL_DMA_ERROR_NOT_SUPPORTED;
        return HAL_ERROR;
    }
    assert_param(IS_DMA_COMPLETELEVEL(CompleteLevel));
    if ((CompleteLevel == HAL_DMA_HALF_TRANSFER) && (hdma->Init.Mode == DMA_MODE_NORMAL_SINGLE))
    {
        hdma->ErrorCode = HAL_DMA_ERROR_NOT_SUPPORTED;
        return HAL_ERROR;
    }
    
    temp = DMA_FLAG_TF_DONE << (hdma->ChannelIndex * 2);
    tickstart = HAL_GetTick();
    
    while (1)
    {
        if (hdma->Init.Mode == DMA_MODE_NORMAL_SINGLE)
        {
            if (__HAL_DMA_GET_FLAG(hdma, temp) != RESET)
            {
                break;
            }
        }
        else 
        {
            if ((hdma->LinkDesc[CompleteLevel]).Valid == RESET)
            {
                break;
            }
        }
        if (Timeout != HAL_MAX_DELAY)
        {
            if ((Timeout == 0U) || ((HAL_GetTick() - tickstart) > Timeout))
            {
                hdma->ErrorCode = HAL_DMA_ERROR_TIMEOUT;
                hdma->State = HAL_DMA_STATE_READY;
                __HAL_UNLOCK(hdma);
                
                return HAL_ERROR;
            }
        }
    }
    
    __HAL_DMA_CLEAR_FLAG(hdma, temp);
    hdma->State = HAL_DMA_STATE_READY;
    __HAL_UNLOCK(hdma);
    
    return HAL_OK;
}

/**
  * @brief  Handles DMA interrupt request.
  * @param  hdma: pointer to a DMA_HandleTypeDef structure that contains
  *               the configuration information for the specified DMA Channel.  
  * @retval None
  */
void HAL_DMA_IRQHandler(DMA_HandleTypeDef *hdma)
{
    uint32_t source_it = hdma->DmaBaseAddress->IM;
    uint32_t flag = DMA_FLAG_TF_DONE << (hdma->ChannelIndex * 2);
    
    if ((__HAL_DMA_GET_FLAG(hdma, flag) != RESET) && ((source_it & flag) == RESET))
    {
        if (hdma->Init.Mode == DMA_MODE_NORMAL_SINGLE)
        {
            __HAL_DMA_DISABLE_IT(hdma, flag);
            hdma->State = HAL_DMA_STATE_READY;
            __HAL_UNLOCK(hdma);
            if (hdma->XferCpltCallback != NULL)
            {
                hdma->XferCpltCallback(hdma);
            }
        }
        else if (hdma->Init.Mode == DMA_MODE_NORMAL_CIRCULAR)
        {
            uint32_t cur_len = (hdma->Instance->CR2 & DMA_CR2_LEN_Msk) >> DMA_CR2_LEN_Pos;
            
            if ((cur_len + hdma->offset) > 0xFFFF)
            {
                cur_len = 0;
                __HAL_DMA_DISABLE(hdma);
                __HAL_DMA_ENABLE(hdma);
            }
            MODIFY_REG(hdma->Instance->CR2, (uint32_t)(DMA_CR2_LEN_Msk), 
                            (uint32_t)((cur_len + hdma->offset) << DMA_CR2_LEN_Pos));
            if (hdma->XferCpltCallback != NULL)
            {
                hdma->XferCpltCallback(hdma);
            }
        }
        else if ((hdma->Init.Mode == DMA_MODE_LINK_SINGLE) || (hdma->Init.Mode == DMA_MODE_LINK_CIRCULAR))
        {
            if (hdma->LinkDesc[0].Valid == 0)
            {
                hdma->LinkDesc[0].Valid = (1 << 31);
                if (hdma->XferHalfCpltCallback != NULL)
                {
                    hdma->XferHalfCpltCallback(hdma);
                }
            }
            else if (hdma->LinkDesc[1].Valid == 0)
            {
                if (hdma->Init.Mode == DMA_MODE_LINK_SINGLE)
                {
                    __HAL_DMA_DISABLE_IT(hdma, flag);
                    hdma->State = HAL_DMA_STATE_READY;
                }
                hdma->LinkDesc[1].Valid = (1 << 31);
                __HAL_UNLOCK(hdma);
                if (hdma->XferCpltCallback != NULL)
                {
                    hdma->XferCpltCallback(hdma);
                }
            }
        }
    }
    __HAL_DMA_CLEAR_FLAG(hdma, flag);
}

/**
  * @brief  Return the DMA hande state.
  * @param  hdma: pointer to a DMA_HandleTypeDef structure that contains
  *               the configuration information for the specified DMA Channel.
  * @retval HAL state
  */
HAL_DMA_StateTypeDef HAL_DMA_GetState(DMA_HandleTypeDef *hdma)
{
    return hdma->State;
}

/**
  * @brief  Return the DMA error code.
  * @param  hdma : pointer to a DMA_HandleTypeDef structure that contains
  *              the configuration information for the specified DMA Channel.
  * @retval DMA Error Code
  */
uint32_t HAL_DMA_GetError(DMA_HandleTypeDef *hdma)
{
    return hdma->ErrorCode;
}

/**
  * @brief  Sets the DMA Transfer parameter.
  * @param  hdma:       pointer to a DMA_HandleTypeDef structure that contains
  *                     the configuration information for the specified DMA Channel.
  * @param  SrcAddress: The source memory Buffer address
  * @param  DstAddress: The destination memory Buffer address
  * @param  DataLength: The length of data to be transferred from source to destination. Unit: Byte
  * @retval HAL status
  */
static void DMA_SetConfig(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint16_t DataLength)
{
    assert_param(IS_DMA_SRC_ADDR(SrcAddress));
    assert_param(IS_DMA_DEST_ADDR(DstAddress));
    assert_param(IS_DMA_LENGTH(hdma->Init.DataAlignment, DataLength));

    hdma->DmaBaseAddress->IF = ((DMA_IF_TRANSFER_DONE | DMA_IF_BURST_DONE) << (hdma->ChannelIndex * 2));
    
    if (hdma->Init.Mode == DMA_MODE_NORMAL_SINGLE)
    {
        hdma->Instance->SA = SrcAddress;
        hdma->Instance->DA = DstAddress;
        MODIFY_REG(hdma->Instance->CR2, DMA_CR2_LEN, (DataLength << DMA_CR2_LEN_Pos));
    }
    else if (hdma->Init.Mode == DMA_MODE_NORMAL_CIRCULAR)
    {
        hdma->Instance->SA = SrcAddress;
        hdma->Instance->DA = DstAddress;
        hdma->Instance->SWA = SrcAddress;
        hdma->Instance->DWA = DstAddress;
        hdma->Instance->WLEN = 0;
        if (hdma->Init.SrcInc == DMA_SINC_CIRCULAR)
        {
            MODIFY_REG(hdma->Instance->WLEN, DMA_WLEN_S_Msk, (DataLength << DMA_WLEN_S_Pos));
        }
        if (hdma->Init.DestInc == DMA_DINC_CIRCULAR)
        {
            MODIFY_REG(hdma->Instance->WLEN, DMA_WLEN_D_Msk, (DataLength << DMA_WLEN_D_Pos));
        }
        hdma->offset = DataLength;
        MODIFY_REG(hdma->Instance->CR2, DMA_CR2_LEN, (DataLength << DMA_CR2_LEN_Pos));
    }
    else
    {
        assert_param(IS_DMA_LINK_LENGTH(DataLength));
        hdma->LinkDesc[0].Control |= ((DataLength / 2) << 7);
        hdma->LinkDesc[0].Valid = (1 << 31);
        hdma->LinkDesc[0].SrcAddr = SrcAddress;
        hdma->LinkDesc[0].DestAddr = DstAddress;
        
        hdma->LinkDesc[1].Control |= ((DataLength - (DataLength / 2)) << 7);
        hdma->LinkDesc[1].Valid = (1 << 31);
        if (hdma->Init.SrcInc == DMA_SINC_ENABLE)
        {
            hdma->LinkDesc[1].SrcAddr = SrcAddress + (DataLength / 2);
        }
        else
        {
            hdma->LinkDesc[1].SrcAddr = SrcAddress;
        }
        
        if (hdma->Init.DestInc == DMA_DINC_ENABLE)
        {
            hdma->LinkDesc[1].DestAddr = DstAddress + (DataLength / 2);
        }
        else
        {
            hdma->LinkDesc[1].DestAddr = DstAddress;
        }
        WRITE_REG(hdma->Instance->LA, (uint32_t)(hdma->LinkDesc));
    }
}
